package ru.hangman.donkeykong.logic;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.graphics.Point;

/**
 * Class for working with level representation.
 * @author pavel.todorov
 */
public class LevelRepresentation {
	
	/**
	 * Enum for point types description.
	 * @author pavel.todorov
	 */
	public enum PointType {
		NotFound,	//!< Point not found.
		Fixed		//!< Point fixed.
	}
	
	/**
	 * Surface types.
	 * @author pavel.todorov
	 */
	public enum SurfaceTypes {
		Normal_surface,		//!< Surface with normal behavior.
		Hole_to_death,		//!< It's hole with no escape.
		Hole_to_level		//!< It's hole to lower level.
	}
	
	/**
	 * Base class for any level element
	 * @author pavel.todorov
	 */
	class BaseElement {
		///Begin coordinate.
		public Point _begin;
		
		///End coordinate.
		public Point _end;
		
		SurfaceTypes _type = SurfaceTypes.Normal_surface;
		
		public BaseElement(int x1, int y1, int x2, int y2) {
			int _x1, _x2, _y1, _y2;
			_x1 = Math.min(x1, x2);
			_y1 = Math.max(y1, y2);
			_x2 = Math.max(x1, x2);
			_y2 = Math.min(y1, y2);
			_begin = new Point(_x1, _y1);
			_end = new Point(_x2, _y2);
		}
		
		public Point getCenter() {
			return new Point((_begin.x + _end.x)/2, (_begin.y + _end.y)/2);
		}
		
		public Point getPointForX(int x) {
			if((x < _begin.x) || (x > _end.x))
				return null;
			float a = (float)(_begin.y - _end.y)/(float)(_begin.x - _end.x);
			float b = (float)_begin.y - a*(float)_begin.x;
			int y = Math.round(a * (float)x + b);
			return new Point(x, y);
		}
		
		public SurfaceTypes getSurfaceType() {
			return _type;
		}
		
		public void setSurfaceType(SurfaceTypes type) {
			_type = type;
		}
	}
	
	/**
	 * Class for level point description.
	 * @author pavel.todorov
	 */
	public class PointDescription {
		private Point _point = null;
		private PointType _pointType = PointType.NotFound;
		private SurfaceTypes _surfaceType = SurfaceTypes.Normal_surface;
		
		public Point getPoint() {
			return _point;
		}
		
		public void setPoint(Point point) {
			_point = point;
		}
		
		public PointType getPointType() {
			return _pointType;
		}
		
		public void setPointType(PointType pointType) {
			_pointType = pointType;
		}
		
		public SurfaceTypes getSurfaceType() {
			return _surfaceType;
		}
		
		public void setSurfaceType(SurfaceTypes surfaceType) {
			_surfaceType = surfaceType;
		}
	}
	
	/// List of all level elements.
	ArrayList<BaseElement> _elements = new ArrayList<BaseElement>();
	
	/**
	 * Constructor
	 * @param path Path to xml-file with level representation.
	 */
	public LevelRepresentation(InputStream stream) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document dom = builder.parse(stream);
			Element root = dom.getDocumentElement();
			NodeList items = root.getElementsByTagName("surface");
			for(int i = 0; i < items.getLength(); i++) {
				Node item = items.item(i);
				NamedNodeMap nodeMap = item.getAttributes();
				int x1 = Integer.parseInt(nodeMap.getNamedItem("x1").getNodeValue());
				int y1 = Integer.parseInt(nodeMap.getNamedItem("y1").getNodeValue());
				int x2 = Integer.parseInt(nodeMap.getNamedItem("x2").getNodeValue());
				int y2 = Integer.parseInt(nodeMap.getNamedItem("y2").getNodeValue());
				_elements.add(new BaseElement(x1, y1, x2, y2));
			}
			Comparator<BaseElement> comparator = new Comparator<BaseElement>() {
				@Override
				public int compare(BaseElement lhs, BaseElement rhs) {
					return ((new Integer(lhs.getCenter().x)).compareTo(new Integer(rhs.getCenter().x)));
				}
			};
			Collections.sort(_elements, comparator);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Get point where person could stay.
	 * @param offset Level horizontal offset.
	 * @return Point with coordinates to person stay.
	 */
	public PointDescription getFixedPointFor(Point point) {
		BaseElement finded = null;
		PointDescription pointDescription = new PointDescription();
		for(BaseElement element: _elements) {
			if((element._begin.x <= point.x) && (element._end.x >= point.x)) {
				if(finded == null)
					finded = element;
				else {
					Point posFinded = finded.getPointForX(point.x);
					Point posElement = element.getPointForX(point.x);
					if((posFinded == null) || (posElement == null))
						continue;
					if(Math.abs(posFinded.y - point.y) > Math.abs(posElement.y - point.y))
						finded = element;
				}
			}
		}
		if(finded != null) {
			pointDescription.setPoint(new Point(point.x, finded.getPointForX(point.x).y));
			pointDescription.setPointType(PointType.Fixed);
			pointDescription.setSurfaceType(finded.getSurfaceType());
		}
		return pointDescription;
	}
}
